package system.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import common.config.datasource.DataSourceConstants;
import common.config.jwt.JwtUtil;
import common.config.jwt.TokenInfo;
import common.mapper.CommonSysAuthorityMapper;
import common.model.converter.CommonSysAuthorityConverter;
import common.model.dto.LoginToken;
import common.model.enums.SysAuthorityTypeEnum;
import common.model.po.SysAuthority;
import common.model.po.SysRoleAuthority;
import common.model.response.base.Result;
import common.model.vo.ButtonVo;
import common.model.vo.MenuVo;
import common.service.CommonSysAuthorityService;
import common.service.CommonSysRoleAuthorityService;
import common.service.RedissonCacheService;
import common.util.CompareUtil;
import common.util.HttpRequestUtil;
import common.util.RouteUtil;
import common.util.TreeUtil;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RMap;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import system.model.compare.SysRoleAuthorityCompare;
import system.model.converter.SysAuthorityConverter;
import system.model.converter.SysRoleAuthorityConverter;
import system.model.request.SaveRoleAuthorityRequest;
import system.model.response.AuthorityModifiedResponse;
import system.model.vo.AuthorityTreeNodeVo;
import system.service.SysAuthorityService;
import system.util.MenuTreeBuilder;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 服务实现类
 *
 * @author 米泽鹏
 * @since 2022-03-11
 */
@Slf4j
@Service
public class SysAuthorityServiceImpl extends ServiceImpl<CommonSysAuthorityMapper, SysAuthority> implements SysAuthorityService {

	@Resource
	private CommonSysAuthorityMapper mapper;
	@Resource
	private CommonSysRoleAuthorityService roleAuthorityService;
	@Resource
	private CommonSysAuthorityService commonAuthorityService;
	@Resource
	private RedissonCacheService redissonCacheService;

	@Override
	public Map<String, Object> getAuthorityInfoFromCache() {
		// 从缓存中获取菜单数据
		List<SysAuthority> authorityList = redissonCacheService.getSysAuthorityCache();
		List<AuthorityTreeNodeVo> menuTreeNodeList = SysAuthorityConverter.poListToAuthorityTreeNodeVoList(authorityList);
		List<AuthorityTreeNodeVo> roots = MenuTreeBuilder.buildMenuTree(menuTreeNodeList);
		roots.sort(Comparator.comparing(AuthorityTreeNodeVo::getSort));
		Map<String, Object> map = Maps.newHashMap();
		map.put("authorityList", authorityList);
		map.put("authorityTree", roots);
		return map;
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	@DS(DataSourceConstants.MASTER_DATASOURCE)
	public Result<?> saveAuthority(SysAuthority po) {
		Long authId = po.getId();
		boolean updateFlag = authId != null;
		// 更新前的数据
		SysAuthority original = null;
		// 是菜单类型时，校验名称是否重复
		SysAuthority oneByName = null;
		Integer type = po.getType();
		if (SysAuthorityTypeEnum.MENU.getValue().equals(type)) {
			String name = po.getName();
			if (StrUtil.isNotBlank(name)) {
				oneByName = lambdaQuery().eq(SysAuthority::getName, name).eq(SysAuthority::getType, SysAuthorityTypeEnum.MENU.getValue()).one();
			}
		}
		// 校验页面组件的资源编码是否重复
		String resources = po.getResources();
		SysAuthority oneByResources = null;
		if (StrUtil.isNotBlank(resources)) {
			oneByResources = lambdaQuery().eq(SysAuthority::getResources, resources).one();
		}
		// 新增
		if (authId == null) {
			Assert.isTrue(!SysAuthorityTypeEnum.MENU.getValue().equals(type) || oneByName == null, "名称已被使用");
			Assert.isNull(oneByResources, "资源编码已被使用");
			save(po);
		}
		// 更新
		else {
			Assert.isTrue(!SysAuthorityTypeEnum.MENU.getValue().equals(type) || (oneByName == null || authId.equals(oneByName.getId())), "名称已被使用");
			Assert.isTrue(oneByResources == null || authId.equals(oneByResources.getId()), "资源编码已被使用");
			original = getById(authId);
			Assert.notNull(original, "权限不存在");
			po.updateById();
		}
		// 更新后的所有权限
		List<SysAuthority> authorityList = list();
		// 变更上级节点或更改权限类型后，会影响角色-权限关联关系，受影响的角色需要重新构建角色-权限关联关系
		List<SysRoleAuthority> roleAuthorityListToAdd = null;
		AuthorityModifiedResponse authorityModifiedResponse = null;
		if (updateFlag) {
			if (!original.getParentId().equals(po.getParentId()) || !original.getType().equals(po.getType()) && (isMenuType(original) || isMenuType(po))) {
				// 找出哪些角色受影响
				Set<Long> roleIdSet = redissonCacheService.getAllRoleIdCacheByAuthId().get(authId);
				if (!roleIdSet.isEmpty()) {
					// 构建权限树
					List<AuthorityTreeNodeVo> menuTreeNodeList = SysAuthorityConverter.poListToAuthorityTreeNodeVoList(authorityList);
					List<AuthorityTreeNodeVo> roots = MenuTreeBuilder.buildMenuTree(menuTreeNodeList);
					roots.sort(Comparator.comparing(AuthorityTreeNodeVo::getSort));
					// 设置每个节点的parent属性
					TreeUtil.treeIteratorDown(roots, node -> {
						List<AuthorityTreeNodeVo> children = node.getChildren();
						if (CollUtil.isNotEmpty(children)) {
							children.forEach(child -> child.setParent(node));
						}
					}, AuthorityTreeNodeVo::getChildren);
					// 为每个受影响的角色重新构建角色-权限关联关系
					roleAuthorityListToAdd = Lists.newArrayList();
					for (Long roleId : roleIdSet) {
						// 老位置所有的上级节点保持不动，新位置所有的上级节点需要添加
						Set<Long> authIdSetToAdd = Sets.newHashSet();
						TreeUtil.findInTree(roots, node -> authId.equals(node.getId()), "children").ifPresent(node -> TreeUtil.treeIteratorUp(node, parent -> {
							Long parentId = parent.getId();
							if (!authId.equals(parentId)) {
								authIdSetToAdd.add(parentId);
							}
						}, "parent"));
						for (Long authIdToAdd : authIdSetToAdd) {
							SysRoleAuthority roleAuthority = new SysRoleAuthority();
							roleAuthority.setRoleId(roleId);
							roleAuthority.setAuthId(authIdToAdd);
							roleAuthorityListToAdd.add(roleAuthority);
						}
					}
					roleAuthorityService.saveBatch(roleAuthorityListToAdd);
				}
				// 查询当前登录用户具有的角色
				LoginToken loginToken = JwtUtil.parseLoginToken(JwtUtil.getTokenStringFromRequest(HttpRequestUtil.getRequest()));
				RMap<Long, Set<Long>> roleIdCacheByUserId = redissonCacheService.getAllRoleIdCacheByUserId();
				Set<Long> currentUserRoleIdSet = roleIdCacheByUserId.get(loginToken.getUserId());
				// 如果影响当前登录用户，则生成并返回新的token
				if (currentUserRoleIdSet.stream().anyMatch(roleIdSet::contains)) {
					authorityModifiedResponse = createAuthorityModifiedResponse(loginToken);
				}
			}
		}
		// 更新缓存
		redissonCacheService.cacheSysAuthority(authorityList).throwExceptionIfFailed();
		if (CollUtil.isNotEmpty(roleAuthorityListToAdd)) {
			redissonCacheService.cacheSysRoleAuthority(roleAuthorityService.list()).throwExceptionIfFailed();
		}
		return Result.successWithData(authorityModifiedResponse);
	}

	/**
	 * 判断权限是否为菜单或者仅结构类型
	 */
	private boolean isMenuType(SysAuthority authority) {
		return SysAuthorityTypeEnum.MENU.getValue().equals(authority.getType()) || SysAuthorityTypeEnum.STRUCTURE.getValue().equals(authority.getType());
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	@DS(DataSourceConstants.MASTER_DATASOURCE)
	public Result<?> deleteAuthority(Long id) {
		// 找到所有下级权限
		Set<Long> parentIdSet = Sets.newHashSet();
		parentIdSet.add(id);
		Set<Long> deleteIdSet = Sets.newHashSet();
		deleteIdSet.add(id);
		List<SysAuthority> children;
		do {
			children = lambdaQuery().in(SysAuthority::getParentId, parentIdSet).select(SysAuthority::getId).list();
			deleteIdSet.addAll(children.stream().map(SysAuthority::getId).collect(Collectors.toSet()));
			parentIdSet = children.stream().map(SysAuthority::getId).collect(Collectors.toSet());
		} while (!parentIdSet.isEmpty());
		// 删除权限
		if (removeBatchByIds(deleteIdSet)) {
			// 更新缓存
			redissonCacheService.cacheSysAuthority(list()).throwExceptionIfFailed();
		}
		// 删除权限和角色的关联关系
		if (roleAuthorityService.lambdaUpdate().in(SysRoleAuthority::getAuthId, deleteIdSet).remove()) {
			// 更新缓存
			redissonCacheService.cacheSysRoleAuthority(roleAuthorityService.list()).throwExceptionIfFailed();
		}
		// 如果删除了自己具有的权限，则生成并返回新的token
		LoginToken loginToken = JwtUtil.parseLoginToken(JwtUtil.getTokenStringFromRequest(HttpRequestUtil.getRequest()));
		Set<Long> authorityIdSet = loginToken.getAuthorityIdSet();
		AuthorityModifiedResponse authorityModifiedResponse = null;
		if (authorityIdSet.contains(id)) {
			authorityModifiedResponse = createAuthorityModifiedResponse(loginToken);
		}
		return Result.successWithData(authorityModifiedResponse);
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	@DS(DataSourceConstants.MASTER_DATASOURCE)
	public Result<?> saveRoleAuthority(SaveRoleAuthorityRequest request) {
		SaveRoleAuthorityRequest.Role role = request.getRole();
		List<SaveRoleAuthorityRequest.Authority> authorityList = request.getAuthorityList();
		List<SysRoleAuthorityCompare> destinationCompareList = Lists.newArrayList();
		for (SaveRoleAuthorityRequest.Authority authority : authorityList) {
			SysRoleAuthorityCompare compare = new SysRoleAuthorityCompare();
			compare.setRoleId(role.getId());
			compare.setRoleCode(role.getRoleCode());
			compare.setRoleName(role.getRoleName());
			compare.setAuthId(authority.getId());
			compare.setAuthName(authority.getName());
			destinationCompareList.add(compare);
		}
		List<SysRoleAuthority> currentPoList = roleAuthorityService.lambdaQuery().eq(SysRoleAuthority::getRoleId, role.getId()).list();
		List<SysRoleAuthorityCompare> currentCompareList = SysRoleAuthorityConverter.poListToCompareList(currentPoList);
		List<Long> deleteIdList = CompareUtil.getDeleteIdListFromEntity(currentCompareList, destinationCompareList);
		List<SysRoleAuthority> insertList = SysRoleAuthorityConverter.compareListToPoList(CompareUtil.getInsertList(currentCompareList, destinationCompareList));
		roleAuthorityService.removeBatchByIds(deleteIdList);
		roleAuthorityService.saveBatch(insertList);
		AuthorityModifiedResponse authorityModifiedResponse = null;
		if (!deleteIdList.isEmpty() || !insertList.isEmpty()) {
			// 查询当前登录用户具有的角色
			LoginToken loginToken = JwtUtil.parseLoginToken(JwtUtil.getTokenStringFromRequest(HttpRequestUtil.getRequest()));
			RMap<Long, Set<Long>> roleIdCacheByUserId = redissonCacheService.getAllRoleIdCacheByUserId();
			Set<Long> roleIdSet = roleIdCacheByUserId.get(loginToken.getUserId());
			// 如果修改了自己具有的角色的权限，则生成并返回新的token
			if (roleIdSet.contains(role.getId())) {
				authorityModifiedResponse = createAuthorityModifiedResponse(loginToken);
			}
			// 更新缓存
			redissonCacheService.cacheSysRoleAuthority(roleAuthorityService.list()).throwExceptionIfFailed();
		}
		return Result.successWithData(authorityModifiedResponse);
	}

	private AuthorityModifiedResponse createAuthorityModifiedResponse(LoginToken loginToken) {
		AuthorityModifiedResponse authorityModifiedResponse = new AuthorityModifiedResponse();
		// 修改后自己具有的权限id
		Set<Long> newAuthIdSet = Sets.newHashSet(commonAuthorityService.getAllAuthIdByUserId(loginToken.getUserId()));
		// 生成新的token
		LoginToken newLoginToken = new LoginToken(loginToken.getUserId(), loginToken.getUsername(), loginToken.getNickname(), loginToken.getRoleIdSet(), newAuthIdSet);
		TokenInfo tokenInfo = JwtUtil.createLoginToken(newLoginToken);
		authorityModifiedResponse.setToken(tokenInfo.getToken());
		authorityModifiedResponse.setExpire(tokenInfo.getExpireDate());
		// 更新token缓存
		redissonCacheService.cacheLoginTokenString(loginToken.getUserId(), tokenInfo.getToken(), tokenInfo.getExpireDate());
		// 返回新的菜单和按钮权限
		if (CollectionUtil.isNotEmpty(newAuthIdSet)) {
			Set<ButtonVo> buttons = Sets.newHashSet();
			List<SysAuthority> sysAuthorityList = commonAuthorityService.listByIds(newAuthIdSet);
			List<SysAuthority> authorityMenuList = Lists.newArrayList();
			sysAuthorityList.forEach(authority -> {
				if (SysAuthorityTypeEnum.MENU.getValue().equals(authority.getType()) || SysAuthorityTypeEnum.STRUCTURE.getValue().equals(authority.getType())) {
					authorityMenuList.add(authority);
				}
				if (SysAuthorityTypeEnum.BUTTON.getValue().equals(authority.getType())) {
					ButtonVo buttonVO = CommonSysAuthorityConverter.poToButtonVO(authority);
					buttons.add(buttonVO);
				}
			});
			Set<MenuVo> menus = RouteUtil.buildRoutes(authorityMenuList);
			authorityModifiedResponse.setMenus(menus);
			authorityModifiedResponse.setButtons(buttons);
		} else {
			authorityModifiedResponse.setMenus(Collections.emptySet());
			authorityModifiedResponse.setButtons(Collections.emptySet());
		}
		return authorityModifiedResponse;
	}

}
